package pentago.engine;

import java.awt.Point;

import pentago.exceptions.PentagoInvalidGameStateException;
import pentago.exceptions.PentagoInvalidMarblePositionException;
import pentago.exceptions.PentagoInvalidMoveException;
import pentago.exceptions.PentagoInvalidPlatePositionException;
import pentago.exceptions.PentagoInvalidPositionException;
import pentago.exceptions.PentagoInvalidRotationException;
import pentago.exceptions.PentagoUnknownPlayerException;

public class GameEngine {

    public static final byte STOPPED = 0;
    public static final byte IN_PROGRESS = 1;
    public static final byte FIRST_WON = 2;
    public static final byte SECOND_WON = 3;
    public static final byte TIE = 4;

    public static final byte NO_PLAYER = 0;
    public static final byte FIRST_PLAYER = 1;
    public static final byte SECOND_PLAYER = 2;

    private GameBoard gameBoard;
    private Move lastMove = null;
    private IPentagoAlgorithm firstPlayerAlgorithm = null;
    private IPentagoAlgorithm secondPlayerAlgorithm = null;
    private boolean gameActive = false;
    private boolean marbleAdded = false;
    private boolean plateRotated = false;
    private byte currentPlayer;
    private byte gameState;
    private byte firstPlayerMode = GameOptions.HUMAN_PLAYER;
    private byte secondPlayerMode = GameOptions.HUMAN_PLAYER;
    private long maxRunTimeFirst;
    private long maxRunTimeSecond;

    public GameEngine(GameBoard gameBoard) throws PentagoInvalidMoveException,
	    PentagoUnknownPlayerException, PentagoInvalidGameStateException {
	this.gameBoard = gameBoard;
	deriveGameState();
	lastMove = new Move();
    }

    public byte getCurrentPlayer() {
	return currentPlayer;
    }

    public void rotationArrowClicked(byte platePosition, byte direction) {

	if (!gameActive)
	    return;

	if (plateRotated) {
	    if (lastMove.getRotatedPlate() == platePosition
		    && lastMove.getRotation() != direction) {
		plateRotated = false;
		try {
		    lastMove.setRotatedPlate(GameBoard.UNKNOWN_POSITION);
		    lastMove.setRotation(Move.UNKNOWN_ROTATION_DIRECTION);
		    gameBoard.rotatePlate(platePosition, direction);
		    if (marbleAdded
			    && platePosition == gameBoard
				    .getPlateFromMarbleTablePosition(lastMove
					    .getPosition()))
			lastMove.rotateMarble(platePosition, direction);
		} catch (PentagoInvalidPlatePositionException e) {
		} catch (PentagoInvalidRotationException e) {
		} catch (PentagoInvalidMarblePositionException e) {
		    e.printStackTrace();
		}
	    }
	} else {
	    plateRotated = true;
	    try {
		lastMove.setRotatedPlate(platePosition);
		lastMove.setRotation(direction);
		gameBoard.rotatePlate(platePosition, direction);
		if (marbleAdded
			&& platePosition == gameBoard
				.getPlateFromMarbleTablePosition(lastMove
					.getPosition()))
		    lastMove.rotateMarble(platePosition, direction);
	    } catch (PentagoInvalidPlatePositionException e) {
		e.printStackTrace();
	    } catch (PentagoInvalidRotationException e) {
		e.printStackTrace();
	    } catch (PentagoInvalidMarblePositionException e) {
		e.printStackTrace();
	    }
	}

	updateGameState();
    }

    public void plateClicked(byte platePosition, Point clickPosition) {
	if (!gameActive)
	    return;

	switch (platePosition) {
	case GameBoard.UPPER_RIGHT:
	    clickPosition.x += 3;
	    break;
	case GameBoard.LOWER_LEFT:
	    clickPosition.y += 3;
	    break;
	case GameBoard.LOWER_RIGHT:
	    clickPosition.x += 3;
	    clickPosition.y += 3;
	    break;
	default:
	    break;
	}

	if (!gameBoard.positionEmpty(clickPosition))
	    return;

	if (marbleAdded) {
	    try {
		gameBoard.removeMarble(lastMove.getPosition());
		gameBoard.addMarble(clickPosition, currentPlayer);
		lastMove.setPosition(clickPosition);
	    } catch (PentagoInvalidMoveException e) {
		return;
	    } catch (PentagoInvalidPositionException e) {
		e.printStackTrace();
	    } catch (PentagoUnknownPlayerException e) {
		e.printStackTrace();
	    }
	} else {
	    try {
		gameBoard.addMarble(clickPosition, currentPlayer);
		lastMove.setPosition(clickPosition);
	    } catch (PentagoInvalidMoveException e) {
		return;
	    } catch (PentagoUnknownPlayerException e) {
		e.printStackTrace();
	    } catch (PentagoInvalidPositionException e) {
		e.printStackTrace();
	    }
	    marbleAdded = true;
	}

	updateGameState();
    }

    public void confirmClicked() {
	if (!gameActive)
	    return;
	if (lastMove.executeMove()) {
	    marbleAdded = false;
	    plateRotated = false;
	    currentPlayer = currentPlayer == FIRST_PLAYER ? SECOND_PLAYER
		    : FIRST_PLAYER;
	    RotationArrow.resetHighlight();
	}
    }

    public boolean currentPlayerHuman() {
	return (currentPlayer == FIRST_PLAYER && firstPlayerMode == GameOptions.HUMAN_PLAYER)
		|| (currentPlayer == SECOND_PLAYER && secondPlayerMode == GameOptions.HUMAN_PLAYER);
    }

    public void executeNextComputerMove() {
	if (currentPlayer == FIRST_PLAYER) {
	    Move move = firstPlayerAlgorithm.getNextMove(
		    gameBoard.getBoardState(), currentPlayer, maxRunTimeFirst);
	    try {
		gameBoard.addMarble(move.getPosition(), currentPlayer);
		updateGameState();
		if (gameState != IN_PROGRESS) {
		    return;
		}
		gameBoard.rotatePlate(move.getRotatedPlate(),
			move.getRotation());
	    } catch (PentagoInvalidMoveException e) {
		e.printStackTrace();
	    } catch (PentagoUnknownPlayerException e) {
		e.printStackTrace();
	    } catch (PentagoInvalidRotationException e) {
		e.printStackTrace();
	    } catch (PentagoInvalidPlatePositionException e) {
		e.printStackTrace();
	    }
	} else {
	    Move move = secondPlayerAlgorithm.getNextMove(
		    gameBoard.getBoardState(), currentPlayer, maxRunTimeSecond);
	    try {
		gameBoard.addMarble(move.getPosition(), currentPlayer);
		updateGameState();
		if (gameState != IN_PROGRESS) {
		    return;
		}
		gameBoard.rotatePlate(move.getRotatedPlate(),
			move.getRotation());
	    } catch (PentagoInvalidMoveException e) {
		e.printStackTrace();
	    } catch (PentagoUnknownPlayerException e) {
		e.printStackTrace();
	    } catch (PentagoInvalidRotationException e) {
		e.printStackTrace();
	    } catch (PentagoInvalidPlatePositionException e) {
		e.printStackTrace();
	    }
	}
	currentPlayer = currentPlayer == FIRST_PLAYER ? SECOND_PLAYER
		: FIRST_PLAYER;
	updateGameState();
    }

    // private byte checkGameEnd() {
    // byte[][] boardState = gameBoard.getBoardState();
    //
    // int howManyInRow;
    // for (int x = 0; x < 6; x++) {
    // for (int y = 0; y < 6; y++) {
    // if (boardState[x][y] == currentPlayer) {
    // howManyInRow = countMarblesInRow(x, y, boardState);
    // if (howManyInRow >= 5)
    // return true;
    // }
    // }
    // }
    // return false;
    // }

    public void beginGame() {
	gameActive = true;
    }

    public void endGame() {
	gameActive = false;
    }

    public byte getGameState() {
	return gameState;
    }

    public void setFirstPlayerMode(byte playerMode, int depth) {
	switch (playerMode) {
	case GameOptions.COMPUTER_RANDOM:
	    firstPlayerAlgorithm = new RandomAlgorithm();
	    firstPlayerMode = GameOptions.COMPUTER_RANDOM;
	    break;
	case GameOptions.COMPUTER_ALPHA_BETA:
	    firstPlayerAlgorithm = new AlphaBetaAlgorithm(depth);
	    firstPlayerMode = GameOptions.COMPUTER_ALPHA_BETA;
	    break;
	case GameOptions.COMPUTER_OTHER:
	    firstPlayerAlgorithm = new TTAlphaBetaAlgorithm(depth);
	    firstPlayerMode = GameOptions.COMPUTER_OTHER;
	    break;
	default:
	    firstPlayerAlgorithm = null;
	    firstPlayerMode = GameOptions.HUMAN_PLAYER;
	}
    }

    public void setSecondPlayerMode(byte playerMode, int depth) {
	switch (playerMode) {
	case GameOptions.COMPUTER_RANDOM:
	    secondPlayerAlgorithm = new RandomAlgorithm();
	    secondPlayerMode = GameOptions.COMPUTER_RANDOM;
	    break;
	case GameOptions.COMPUTER_ALPHA_BETA:
	    secondPlayerAlgorithm = new AlphaBetaAlgorithm(depth);
	    secondPlayerMode = GameOptions.COMPUTER_ALPHA_BETA;
	    break;
	case GameOptions.COMPUTER_OTHER:
	    secondPlayerAlgorithm = new TTAlphaBetaAlgorithm(depth);
	    secondPlayerMode = GameOptions.COMPUTER_OTHER;
	    break;
	default:
	    secondPlayerAlgorithm = null;
	    secondPlayerMode = GameOptions.HUMAN_PLAYER;
	}
    }

    private void deriveGameState() throws PentagoInvalidGameStateException {
	byte[][] boardState;
	int firstPlayerCount, secondPlayerCount;

	updateGameState();

	boardState = gameBoard.getBoardState();
	firstPlayerCount = secondPlayerCount = 0;
	for (int i = 0; i < 6; ++i)
	    for (int j = 0; j < 6; ++j) {
		firstPlayerCount += boardState[i][j] == FIRST_PLAYER ? 1 : 0;
		secondPlayerCount += boardState[i][j] == SECOND_PLAYER ? 1 : 0;
	    }
	if (firstPlayerCount == secondPlayerCount) {
	    currentPlayer = FIRST_PLAYER;
	} else if (firstPlayerCount - 1 == secondPlayerCount) {
	    currentPlayer = SECOND_PLAYER;
	} else {
	    throw new PentagoInvalidGameStateException();
	}
    }

    private void updateGameState() {
	boolean firstWon, secondWon;
	int marblesCount = 0;
	byte[][] boardState = gameBoard.getBoardState();

	for (int i = 0; i < 6; ++i)
	    for (int j = 0; j < 6; ++j) {
		if (boardState[i][j] != NO_PLAYER)
		    marblesCount++;
	    }
	if (marblesCount == 36) {
	    gameState = TIE;
	    gameActive = false;
	    return;
	}

	firstWon = playerWon(FIRST_PLAYER);

	secondWon = playerWon(SECOND_PLAYER);

	gameActive = false;
	if (firstWon && secondWon) {
	    gameState = TIE;
	} else if (firstWon) {
	    gameState = FIRST_WON;
	} else if (secondWon) {
	    gameState = SECOND_WON;
	} else {
	    gameState = IN_PROGRESS;
	    gameActive = true;
	}
    }

    private boolean playerWon(byte player) {
	byte[][] boardState = gameBoard.getBoardState();

	int howManyInRow;
	for (int x = 0; x < 6; x++) {
	    for (int y = 0; y < 6; y++) {
		if (boardState[x][y] == player) {
		    howManyInRow = countMarblesInRow(x, y, boardState);
		    if (howManyInRow >= 5)
			return true;
		}
	    }
	}
	return false;
    }

    private int countMarblesInRow(int x, int y, byte[][] boardState) {
	int howMany = 0;
	byte player = boardState[x][y];
	int i, j;
	i = x;
	j = y;
	while (i < 6) {
	    if (boardState[i][j] != player)
		break;
	    howMany++;
	    i++;
	}
	i = x - 1;
	while (i >= 0) {
	    if (boardState[i][j] != player)
		break;
	    howMany++;
	    i--;
	}
	if (howMany >= 5)
	    return howMany;

	i = x;
	j = y;
	howMany = 0;
	while (j < 6) {
	    if (boardState[i][j] != player)
		break;
	    howMany++;
	    j++;
	}
	j = y - 1;
	while (j >= 0) {
	    if (boardState[i][j] != player)
		break;
	    howMany++;
	    j--;
	}
	if (howMany >= 5)
	    return howMany;

	i = x;
	j = y;
	howMany = 0;
	while (i < 6 && j < 6) {
	    if (boardState[i][j] != player)
		break;
	    howMany++;
	    i++;
	    j++;
	}
	i = x - 1;
	j = y - 1;
	while (i >= 0 && j >= 0) {
	    if (boardState[i][j] != player)
		break;
	    howMany++;
	    i--;
	    j--;
	}
	if (howMany >= 5)
	    return howMany;

	i = x;
	j = y;
	howMany = 0;
	while (i >= 0 && j < 6) {
	    if (boardState[i][j] != player)
		break;
	    howMany++;
	    i--;
	    j++;
	}
	i = x + 1;
	j = y - 1;
	while (i < 6 && j >= 0) {
	    if (boardState[i][j] != player)
		break;
	    howMany++;
	    i++;
	    j--;
	}
	return howMany;
    }

    public void setMaxRunTimeFirst(long maxRunTimeFirst) {
	this.maxRunTimeFirst = maxRunTimeFirst;
    }

    public void setMaxRunTimeSecond(long maxRunTimeSecond) {
	this.maxRunTimeSecond = maxRunTimeSecond;
    }

}
